package neptune.manager.allocator;

import java.io.File;
import java.util.Map;

import neptune.manager.NeptuneManager;
import neptune.manager.connector.Connector;
import neptune.manager.connector.ConnectorException;
import neptune.manager.connector.LibvirtXenConnector;
import neptune.manager.model.ApplicationProperties;
import neptune.manager.model.cluster.Cluster;
import neptune.manager.model.template.VmTemplate.OsDistributions;
import neptune.manager.model.topology.Node;
import neptune.manager.model.topology.NotEnoughResourcesException;
import neptune.manager.model.topology.Topology;
import neptune.manager.model.topology.UndefinedControlInterfaceException;
import neptune.manager.storage.StorageReadException;
import neptune.manager.storage.StorageSaveException;
import neptune.manager.storage.XMLApplicationPropertiesStorageFactory;
import neptune.manager.storage.XMLClusterStorageFactory;
import neptune.manager.utils.DebugLog;

public class AsyncExperimentAllocatorWithMap implements Runnable
{

	private String scriptPath;
	private Map<String, String[]> allocationMap;
	private Topology topology;
	Cluster cluster;
	private String vmTemplateSourcePath;
	private String experimentsVMsPath;
	private String emuPluginRootPath;
	
	private static final String IMG_EXTENSION = ".img";
	private String gatewayControlInterface; // il gateway dell'interfaccia di controllo
	
	public AsyncExperimentAllocatorWithMap (String emuPluginRootPath, String vmTemplatesPath,
			String experimentsVmsPath, Map<String, String[]> allocationMap,Topology topology, Cluster cluster ) throws StorageReadException
	{
		this.emuPluginRootPath=emuPluginRootPath;
		this.scriptPath = emuPluginRootPath + "scripts/clone.sh";
		this.allocationMap=allocationMap;
		this.topology=topology;
		this.cluster=cluster;
		this.vmTemplateSourcePath=vmTemplatesPath;
		this.experimentsVMsPath=experimentsVmsPath;
		String pathConfigFile=emuPluginRootPath+File.separator+"configuration.xml";
		XMLApplicationPropertiesStorageFactory appPropFactory=new XMLApplicationPropertiesStorageFactory(pathConfigFile);
		ApplicationProperties appProperties = appPropFactory.getApplicationPropertiesStorage().getApplicationProperties();
		this.gatewayControlInterface= appProperties.getGatewayControlIpRange();
	}
	
	@Override
	public void run() 
	{
		
		if ( this.isRisorseDisponibili() )
		{
			//		For debug: shows allocation map
			//		for (String pm : allocationMap.keySet()) {
			//			System.out.println(pm);
			//			for (String st : allocationMap.get(pm)) {
			//				System.out.println(st);
			//			}
			//		}
			
			// Aggiungo i task
			String taskId=String.valueOf( System.currentTimeMillis() );
			for (String machineName : allocationMap.keySet()) 
			{
				for (String vmName : allocationMap.get(machineName))
				{
					Job job=new Job();
					job.setDescription("Deploy of virtual machine "+vmName);
					job.setId(vmName);
					job.setTaskId(taskId);
					JobQueue.getInstance().enqueue(job);
				}
			}
			
			for (String machineName : allocationMap.keySet()) 
			{
				try 
				{
					Connector connector = NeptuneManager.instance().getConnector(
							machineName);
	
					for (String vmName : allocationMap.get(machineName)) {
	
						Node node = topology.searchNodeIntoTopology(vmName);
	
						if (node == null)
						{
							String msg="Node '" + vmName
							+ "' is undefined in topology '"
							+ topology.getName() + "'";
							new RuntimeException(msg);
							/*
							throw new AllocationException("Node '" + vmName
									+ "' is undefined in topology '"
									+ topology.getName() + "'");
							*/
						}
						String[] macAddresses = new String[node.getInterfaces().length];
						for (int i = 0; i < macAddresses.length; i++) {
							macAddresses[i] = node.getInterfaces()[i]
									.getMacAddress().getAddress();
						}
	
						String nodeFullId = node.getId()
								+ NeptuneManager.EXPID_NODEID_SEPARATOR
								+ topology.getName();
	
						int distribution;
						if (node.getOperativeSystem().getName().equals(OsDistributions.Debian.toString())) 
						{
							distribution = LibvirtXenConnector.DIST_DEBIAN;
						}
						else if(node.getOperativeSystem().getName().equals(OsDistributions.CentOS.toString())) 
						{
							distribution = LibvirtXenConnector.DIST_CENTOS;
						}
						else if(node.getOperativeSystem().getName().equals(OsDistributions.Fedora.toString())) 
						{
							distribution = LibvirtXenConnector.DIST_FEDORA;
						}
						else
						{
							distribution = LibvirtXenConnector.DIST_UNDEFINED;
						}
						DebugLog.printDetailedLog("Connector creation",
								MainAllocator.class.getSimpleName());
	
						cluster.getNodeById(machineName).addHostedNode(node,
								topology.getName());
						Job job=null;
						try 
						{
							// cambio lo stato del job
							job=JobQueue.getInstance().getJob(taskId, vmName);
							if (job != null)
							{
								job.setState(Job.STATE_RUNNING);
							}
							// creo la macchina virtuale
							connector.createVM(nodeFullId, node.getControlAddress()
									.getAddress(), node.getControlAddress()
									.getSubnetMask(),
									this.gatewayControlInterface, macAddresses,
									vmTemplateSourcePath
											+ node.getSoftwareTemplate()
											+ IMG_EXTENSION, experimentsVMsPath
											+ nodeFullId + IMG_EXTENSION,
									distribution, node.getRam(), scriptPath,
									"/sbin", "/sbin", null);
							// cambio lo stato del job
							if (job != null)
							{
								job.setState(Job.STATE_FINISH);
							}
							// aggiorno il file cluster.xml
							String pathFileCluster=this.emuPluginRootPath+File.separator+"cluster.xml";
							XMLClusterStorageFactory clusterFactory=new XMLClusterStorageFactory(pathFileCluster);
							clusterFactory.getClusterStorage().saveCluster(this.cluster);
						}
						catch (UndefinedControlInterfaceException e) 
						{
							if (job != null)
							{
								job.setState(Job.STATE_FAILED);
							}
							// Elimina il riferimento all'allocazione di questo nodo
							cluster.getNodeById(machineName).removeHostedNode(node,
									topology.getName());
	
							DebugLog.printDetailedLog("Unable to create vm '"
									+ nodeFullId + "'. Cause: " + e.getMessage(),
									MainAllocator.class.getSimpleName());
						} 
						catch (StorageSaveException e)
						{
							if (job != null)
							{
								job.setState(Job.STATE_FAILED);
							}
							DebugLog.printDetailedLog(e.getMessage(), MainAllocator.class
									.getSimpleName());
							throw new RuntimeException(e);
						}
					}
	
					connector.closeConnection();
	
				}
				catch (ConnectorException e)
				{
					DebugLog.printDetailedLog(e.getMessage(), MainAllocator.class
							.getSimpleName());
					throw new RuntimeException(e);
				} 
				catch (NotEnoughResourcesException e) 
				{
					DebugLog.printDetailedLog(e.getMessage(), MainAllocator.class
							.getSimpleName());
					throw new RuntimeException(e);
				}
			}
		}
		else
		{
			// Non ci sono risorse disponibili per allocare l'esperimento
		}
	}
	
	private boolean isRisorseDisponibili()
	{
		if (this.cluster.getNodes().length== 0 )
		{
			return false;
		}
		return true;
	}

}
